﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace XPathPlugin.XPathTreeUtils
{
    class XPathNode: IDisposable
    {
        #region Variables

        private XPathNode _parent;
        private XPathNodeList _children;
        private XPathValue _value;
        private bool _isDisposed;

        #endregion

        public event EventHandler Disposing;

        public XPathNode()
        {
            _parent = null;
            _children = new XPathNodeList(this);
        }

        public XPathNode(XPathValue value)
        {
            _parent = null;
            _value = value;
            _children = new XPathNodeList(this);
        }

        public XPathNode(XPathNode parent)
        {
            _parent = parent;
            _children = new XPathNodeList(this);
        }

        public XPathNode(XPathNodeList children)
        {
            _parent = null;
            _children = children;
            _children.Parent = this;
        }

        public XPathNode(XPathNode parent, XPathNodeList children)
        {
            _parent = parent;
            _children = children;
            _children.Parent = this;
        }

        #region Properties

        /// <summary>
        /// Gets or sets the parent of this node
        /// </summary>
        public XPathNode Parent
        {
            get { return _parent; }
            set {
                
                if (value == _parent)
                    return;                

                // Remove this node from old parent children
                if (_parent != null)
                    _parent.Children.Remove(this);                

                // Add to new parent this node as child
                if (value != null && !value.Children.Contains(this))
                    value.Children.Add(this);                
                
                // Set new parent 
                _parent = value;
            }
        }

        /// <summary>
        /// Gets root node
        /// </summary>
        public XPathNode Root
        {
            get
            {
                XPathNode node = this;
                while (node.Parent != null)
                {
                    node = node.Parent;
                }
                
                return node;
            }
        }

        /// <summary>
        /// Get this node children
        /// </summary>
        public XPathNodeList Children
        {
            get { return _children; }
            private set { _children = value; }
        }

        /// <summary>
        /// Returns true if node has children
        /// </summary>
        public bool HasChildren
        {
            get { return _children.Count > 0; }
        }

        /// <summary>
        /// Gets or sets this node value
        /// </summary>
        public XPathValue Value
        {
            get { return _value; }
            set { _value = value; }
        }

        /// <summary>
        /// Gets this node depth
        /// </summary>
        public int Depth
        {
            get
            {
                int depth = 0;
                XPathNode node = this;
                while (node.Parent != null)
                {
                    node = node.Parent;
                    depth++;
                }
                return depth;
            }
        }

        /// <summary>
        /// Gets if node is disposed
        /// </summary>
        public bool IsDisposed
        {
            get { return _isDisposed; }
        }

        #endregion

        /// <summary>
        /// Returns preorder depth enumaration 
        /// </summary>
        /// <returns>enumeration</returns>
        public IEnumerator<XPathNode> GetDepthFirstEnumerator()
        {
            yield return this;

            foreach (XPathNode child in Children)
            {
                IEnumerator<XPathNode> enumerator = child.GetDepthFirstEnumerator();
                while (enumerator.MoveNext())
                {
                    yield return enumerator.Current;
                }
            }
        }

        /// <summary>
        /// Gets postorder depth enumeration
        /// </summary>
        /// <returns>enumeration</returns>
        public IEnumerator<XPathNode> GetDepthLastEnumerator()
        {
            return GetDepthLastEnumeratorImpl(this);
        }        

        /// <summary>
        /// Gets preorder breadth enumeration
        /// </summary>
        /// <returns>enumeration</returns>
        public IEnumerator<XPathNode> GetBreadthFirstEnumerator()
        {
            Queue<XPathNode> queue = new Queue<XPathNode>();
            queue.Enqueue(this);
            
            while (0 < queue.Count)
            {
                XPathNode node = queue.Dequeue();
                foreach (XPathNode child in node.Children)
                {
                    queue.Enqueue(child);
                }
                yield return node;
            }
        }

        /// <summary>
        /// Returns string representing this node
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return _value.Value + "; NodeType=" + _value.Type.ToString() + "; JoinType=" + _value.TypeOfJoin.ToString();
        }

        /// <summary>
        /// Implement postorder depth enumeration
        /// </summary>
        /// <param name="node">node to walk in node tree</param>
        /// <returns>enumeration</returns>
        private IEnumerator<XPathNode> GetDepthLastEnumeratorImpl(XPathNode node)
        {
            foreach (XPathNode child in node.Children)
            {
                IEnumerator<XPathNode> enumerator = child.GetDepthLastEnumerator();
                while (enumerator.MoveNext())
                {
                    yield return enumerator.Current;
                }
            }

            yield return this;
        }
       
        #region IDisposable Members               
        
        public virtual void Dispose()
        {
            CheckDisposed();

            foreach (XPathNode node in Children)
            {
                node.Dispose();
            }
            
            OnDisposing();
            
            _isDisposed = true;
        }        

        /// <summary>
        /// Raises disposing event.
        /// </summary>
        protected void OnDisposing()
        {
            if (Disposing != null)            
                Disposing(this, EventArgs.Empty);            
        }

        /// <summary>
        /// Check for disposing state
        /// </summary>
        protected void CheckDisposed()
        {
            if (IsDisposed)
            {
                throw new ObjectDisposedException(GetType().Name);
            }
        }

        #endregion
    }
}
